package sanduche.play.generators

import sanduche.play.shared.ModuleConfig
import scala.collection.JavaConversions._
import java.lang.reflect.Modifier
import sanduche.play.shared.Route
import sanduche.play.shared.RequestMethod
import java.lang.reflect.Method
import sanduche.play.shared.UrlParam
import scala.collection.immutable.HashSet
import scala.collection.immutable.HashMap
import java.lang.annotation.Annotation
import sanduche.play.shared.RequestBody
import sanduche.controllers.MensajeriaController
import java.io.File
import java.io.PrintWriter
import play.db.jpa.Transactional
import sanduche.controllers.UserController
import sanduche.controllers.AcudienteController
import sanduche.controllers.ProfesorController
import sanduche.controllers.SupadminController
import sanduche.controllers.CoordinadorController

object SanducheModuleParser {

  val sanControllers = Seq(classOf[AcudienteController],
    classOf[CoordinadorController],
    classOf[MensajeriaController],
    classOf[ProfesorController],
    classOf[SupadminController],
    classOf[UserController]);

  def apply {
    val modules = sanControllers.filter(clazz => {
      clazz.isAnnotationPresent(classOf[ModuleConfig]);
    }).map(clazz => {
      SanModule(clazz);
    });
    PlayGenerator(modules);
    STJSGenerator(modules);
    JavascriptGenerator(modules);
    AndroidGenerator(modules)
  }

}

case class SanModule(clazz: Class[_]) {

  val name = clazz.getSimpleName.replaceAll("Controller", "").toLowerCase();
  val config = clazz.getAnnotation(classOf[ModuleConfig]);
  val prefix = config.prefix();

  val services = {
    clazz.getMethods().filter(method => {
      val modifiers = method.getModifiers;
      Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && method.isAnnotationPresent(classOf[Route]);
    }).map(method => {
      SanService(method, this);
    });
  }

}

case class EPType(rawType: Class[_], presentAnnotations: Array[Annotation]) {

  val annotations = HashMap(presentAnnotations.map(anotation => {
    (anotation.annotationType().getSimpleName(), anotation);
  }): _*)

  val isIterable = classOf[java.lang.Iterable[_]].isAssignableFrom(rawType);

  val maybeGenericType = {

    val r = getAnnotation(classOf[Route]).flatMap(annotation => {
      annotation.iteratedClass().lift(0);
    })
    if (r.isDefined)
      r;
    else {
      getAnnotation(classOf[RequestBody]).flatMap(annotation => {
        annotation.iteratedClass().lift(0);
      });
    }
  }
  def isAnnotatedWith(clazz: Class[_]) = annotations.contains(clazz.getSimpleName());

  def isType(clazz: Class[_]) = rawType.equals(clazz);

  def getAnnotation[A](clazz: Class[A]) = annotations.get(clazz.getSimpleName()).asInstanceOf[Option[A]];
}

case class SanParameter(epType: EPType, index: Int) {

  val isUrlParam = {
    epType.isAnnotatedWith(classOf[UrlParam]);
  }
  def isQueryParam(url: String) = {
    epType.getAnnotation(classOf[UrlParam]).map { param =>
      url.contains(":" ++ param.value())
    }.getOrElse(false)
  }
  val isBody = {
    !isUrlParam && epType.isAnnotatedWith(classOf[RequestBody]);
  }
  val isRequest = {
    !isUrlParam && !isBody && epType.isType(classOf[play.mvc.Http.Request])

  }
  val isSession = {
    !isUrlParam && !isBody && !isRequest && epType.isType(classOf[play.mvc.Http.Session])
  }
  val isResponse = {
    !isUrlParam && !isBody && !isRequest && !isSession && epType.isType(classOf[play.mvc.Http.Response])
  }
  val paramName = {
    if (isUrlParam) {
      epType.getAnnotation(classOf[UrlParam]).map(_.value()).getOrElse("")
    } else if (isBody)
      "body"
    else if (isRequest)
      "request"
    else if (isSession)
      "session"
    else if (isResponse)
      "response"
    else
      "arg" + index
  }
  val simpleJavaTypeName = {
    epType.rawType.getSimpleName() ++ (epType.maybeGenericType.map("<" ++ _.getSimpleName() ++ ">").getOrElse(""))
  }
  val declaration = {
    simpleJavaTypeName ++ " " ++ paramName
  }

  val value = {
    if (isUrlParam)
      "null"
    else if (isBody)
      "Json.fromJson(request().body().asJson()," ++ simpleJavaTypeName ++ ".class);"
    else if (isRequest)
      "request();"
    else if (isSession)
      "session();"
    else if (isResponse)
      "response();"
    else
      "null"
  }
  val assignment = {
    declaration ++ " = " ++ value;
  }
  val reference = {
    paramName
  }

}

case class SanService(method: Method, moduleParent: SanModule) {

  val params = (method.getParameterTypes(), method.getParameterAnnotations()).zipped.
    toList.
    zipWithIndex
    .map {
      case ((paramType, paramAnotations), index) => {
        SanParameter(EPType(paramType, paramAnotations), index);
      }
    };

  val returnType = {
    EPType(method.getReturnType(), method.getDeclaredAnnotations)
  };
  val route = method.getAnnotation(classOf[Route])
  val urlParams = params.filter(_.isUrlParam)
  val queryParams = urlParams.filter(_.isQueryParam(route.value))
  val maybeBody = params.filter(_.isBody).lift(0)
  val maybeRequest = params.filter(_.isRequest).lift(0)
  val maybeSession = params.filter(_.isSession).lift(0)
  val maybeResponse = params.filter(_.isResponse).lift(0)
  val maybeTransactional = Option(method.getAnnotation(classOf[Transactional]))
  val returns = {
    !method.getReturnType().toString().equals("void")
  }
  val simpleName = method.getName();

}

